/* $Id: VBoxMPMisc.cpp $ */
/** @file
 * VBox WDDM Miniport driver
 */

 /*
  * Copyright (C) 2011-2020 Oracle Corporation
  *
  * This file is part of VirtualBox Open Source Edition (OSE), as
  * available from http://www.virtualbox.org. This file is free software;
  * you can redistribute it and/or modify it under the terms of the GNU
  * General Public License (GPL) as published by the Free Software
  * Foundation, in version 2 as it comes in the "COPYING" file of the
  * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
  * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
  */

#include <stdio.h> /* for swprintf */

#include "VBoxMPMisc.h"
#include "VBoxMPRegistry.h"
#include "VBoxMPWddm.h"
#include "common/VBoxMPUtils.h"
#include "common/VBoxMPDevExt.h"
#include "CommonDef.h"
#include "asm.h"

NTSTATUS vboxWddmRegQueryDisplaySettingsKeyName(PVBOXMP_DEVEXT pDevExt,
        D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId,
        ULONG cbBuf, PWCHAR pBuf, PULONG pcbResult)
{
    NTSTATUS Status = STATUS_SUCCESS;
    PWCHAR pSuffix;
    const WCHAR* pKeyPrefix;
    UINT cbKeyPrefix;
    UNICODE_STRING* pVGuid = vboxWddmVGuidGet(pDevExt);
    if (!pVGuid) {
        LOGE("vboxWddmVGuidGet failed\n");
        return STATUS_UNSUCCESSFUL;
    }

    UINT32 build;
    vboxWinVersion_t ver = VBoxQueryWinVersion(&build);
    if (ver == WINVERSION_VISTA) {
        pKeyPrefix = VBOXWDDM_REG_DISPLAYSETTINGSKEY_PREFIX_VISTA;
        cbKeyPrefix = sizeof (VBOXWDDM_REG_DISPLAYSETTINGSKEY_PREFIX_VISTA);
    } else if (ver >= WINVERSION_10 && build >= 17763) {
        pKeyPrefix = VBOXWDDM_REG_DISPLAYSETTINGSKEY_PREFIX_WIN10_17763;
        cbKeyPrefix = sizeof(VBOXWDDM_REG_DISPLAYSETTINGSKEY_PREFIX_WIN10_17763);
    } else {
        pKeyPrefix = VBOXWDDM_REG_DISPLAYSETTINGSKEY_PREFIX_WIN7;
        cbKeyPrefix = sizeof(VBOXWDDM_REG_DISPLAYSETTINGSKEY_PREFIX_WIN7);
    }

    ULONG cbResult = cbKeyPrefix + pVGuid->Length + 2 + 8;
    if (cbBuf >= cbResult) {
        wcscpy(pBuf, pKeyPrefix);
        pSuffix = pBuf + (cbKeyPrefix-2)/2;
        memcpy(pSuffix, pVGuid->Buffer, pVGuid->Length);
        pSuffix += pVGuid->Length/2;
        pSuffix[0] = L'\\';
        pSuffix += 1;
        swprintf(pSuffix, L"%04d", VidPnSourceId);
    } else {
        Status = STATUS_BUFFER_TOO_SMALL;
    }

    *pcbResult = cbResult;

    return Status;
}

NTSTATUS vboxWddmRegOpenDisplaySettingsKey(IN PVBOXMP_DEVEXT pDevExt,
        D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId, OUT PHANDLE phKey)
{
    WCHAR Buf[512];
    ULONG cbBuf = sizeof(Buf);
    NTSTATUS Status = vboxWddmRegQueryDisplaySettingsKeyName(pDevExt, VidPnSourceId, cbBuf, Buf, &cbBuf);
    if (Status == STATUS_SUCCESS) {
        Status = vboxWddmRegOpenKey(phKey, Buf, GENERIC_READ);
        if(Status == STATUS_SUCCESS) {
            return STATUS_SUCCESS;
        }
    }

    /* fall-back to make the subsequent VBoxVideoCmnRegXxx calls treat the fail accordingly
     * basically needed to make as less modifications to the current XPDM code as possible
     */
    *phKey = NULL;

    return Status;
}

NTSTATUS vboxWddmRegDisplaySettingsQueryRelX(HANDLE hKey, int *pResult)
{
    DWORD dwVal;
    NTSTATUS Status = vboxWddmRegQueryValueDword(hKey,
                      VBOXWDDM_REG_DISPLAYSETTINGS_ATTACH_RELX, &dwVal);
    if (Status == STATUS_SUCCESS) {
        *pResult = (int)dwVal;
    }

    return Status;
}

NTSTATUS vboxWddmRegDisplaySettingsQueryRelY(HANDLE hKey, int *pResult)
{
    DWORD dwVal;
    NTSTATUS Status = vboxWddmRegQueryValueDword(hKey,
                      VBOXWDDM_REG_DISPLAYSETTINGS_ATTACH_RELY, &dwVal);
    if (Status == STATUS_SUCCESS) {
        *pResult = (int)dwVal;
    }

    return Status;
}

NTSTATUS vboxWddmDisplaySettingsQueryPos(IN PVBOXMP_DEVEXT pDevExt,
        D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId, POINT * pPos)
{
    HANDLE hKey;
    NTSTATUS Status = vboxWddmRegOpenDisplaySettingsKey(pDevExt, VidPnSourceId, &hKey);
    if (Status == STATUS_SUCCESS) {
        int x, y;
        Status = vboxWddmRegDisplaySettingsQueryRelX(hKey, &x);
        if (Status == STATUS_SUCCESS) {
            Status = vboxWddmRegDisplaySettingsQueryRelY(hKey, &y);
            if (Status == STATUS_SUCCESS) {
                pPos->x = x;
                pPos->y = y;
            }
        }
        ZwClose(hKey);
    }

    return Status;
}

void vboxWddmDmSetupDefaultVramLocation(PVBOXMP_DEVEXT pDevExt,
    D3DDDI_VIDEO_PRESENT_SOURCE_ID ModifiedVidPnSourceId, VBOXWDDM_SOURCE *paSources)
{
    PVBOXWDDM_SOURCE pSource = &paSources[ModifiedVidPnSourceId];
    ULONG offVram = vboxWddmVramCpuVisibleSegmentSize(pDevExt);
    offVram /= VBoxCommonFromDeviceExt(pDevExt)->cDisplays;
    offVram &= ~PAGE_OFFSET_MASK;
    offVram *= ModifiedVidPnSourceId;

    if (vboxWddmAddrSetVram(&pSource->AllocData.Addr, 1, offVram)) {
        pSource->u8SyncState &= ~VBOXWDDM_HGSYNC_F_SYNCED_LOCATION;
    }
}

VOID vboxWddmCounterU32Wait(UINT32 volatile *pu32, UINT32 u32Val)
{
    LARGE_INTEGER Interval;
    Interval.QuadPart = -(INT64) 2 * 10000; /* ms */
    UINT32 u32CurVal;

    if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
        LOGE("cannot wait in >= DISPATCH_LEVEL\n");
        return;
    }

    while ((u32CurVal = ASMAtomicReadU32(pu32)) != u32Val) {
        KeDelayExecutionThread(KernelMode, FALSE, &Interval);
    }
}

static BOOLEAN vboxWddmSlVSyncIrqCb(PVOID pvContext)
{
    PVBOXMP_DEVEXT pDevExt = (PVBOXMP_DEVEXT)pvContext;
    DXGKARGCB_NOTIFY_INTERRUPT_DATA notify;
    BOOLEAN bNeedDpc = FALSE;
    for (UINT i = 0; i < (UINT)VBoxCommonFromDeviceExt(pDevExt)->cDisplays; ++i)
    {
        PVBOXWDDM_TARGET pTarget = &pDevExt->aTargets[i];
        if (pTarget->fConnected)
        {
            memset(&notify, 0, sizeof(DXGKARGCB_NOTIFY_INTERRUPT_DATA));
            notify.InterruptType = g_VBoxDisplayOnly?
                                       DXGK_INTERRUPT_DISPLAYONLY_VSYNC:
                                       DXGK_INTERRUPT_CRTC_VSYNC;
            notify.CrtcVsync.VidPnTargetId = i;
            pDevExt->u.primary.DxgkInterface.DxgkCbNotifyInterrupt(pDevExt->u.primary.DxgkInterface.DeviceHandle, &notify);
            bNeedDpc = TRUE;
        }
    }

    if (bNeedDpc)
    {
        pDevExt->u.primary.DxgkInterface.DxgkCbQueueDpc(pDevExt->u.primary.DxgkInterface.DeviceHandle);
    }

    return FALSE;
}

static VOID vboxWddmSlVSyncCb(PEX_TIMER Timer, PVOID Context)
{
    PVBOXMP_DEVEXT pDevExt = (PVBOXMP_DEVEXT)Context;

    LONGLONG DueTime = -10000000LL / g_RefreshRate; /* 100ns units per second / Freq Hz */

    EXT_SET_PARAMETERS params;
    ExInitializeSetTimerParameters(&params);
    ExSetTimer(pDevExt->VSyncTimer, DueTime, 0, &params);

    ASMAtomicWriteU32(&pDevExt->fVSyncInVBlank, 1);

    BOOLEAN bDummy;
    NTSTATUS Status = pDevExt->u.primary.DxgkInterface.DxgkCbSynchronizeExecution(
            pDevExt->u.primary.DxgkInterface.DeviceHandle,
            vboxWddmSlVSyncIrqCb,
            pDevExt,
            0, /* IN ULONG MessageNumber */
            &bDummy);
    if (!NT_SUCCESS(Status))
        LOGW("DxgkCbSynchronizeExecution failed Status %#x", Status);

    ASMAtomicWriteU32(&pDevExt->fVSyncInVBlank, 0);
}

NTSTATUS VBoxWddmSlInit(PVBOXMP_DEVEXT pDevExt)
{
    pDevExt->bVSyncTimerEnabled = FALSE;
    pDevExt->fVSyncInVBlank = 0;
    KeQuerySystemTime((PLARGE_INTEGER)&pDevExt->VSyncTime);

    pDevExt->VSyncTimer = ExAllocateTimer(vboxWddmSlVSyncCb, pDevExt, EX_TIMER_HIGH_RESOLUTION);
    if (!pDevExt->VSyncTimer) {
        return STATUS_UNSUCCESSFUL;
    }

    return STATUS_SUCCESS;
}

NTSTATUS VBoxWddmSlEnableVSyncNotification(PVBOXMP_DEVEXT pDevExt, BOOLEAN fEnable)
{
    if (!pDevExt->bVSyncTimerEnabled == !fEnable)
        return STATUS_SUCCESS;

    if (!fEnable)
    {
        LOGI("VSync set disable\n");
        ExCancelTimer(pDevExt->VSyncTimer, NULL);
    }
    else
    {
        LOGI("VSync set enable\n");

        /* The EX_TIMER resolution is 1ms, so if we use the precise period value,
         * the actual refresh rate will be little less that g_RefreshRate. Here
         * we calculate the period with ms unit firstly to get a shorter value
         * than the precise value. 1ms is 10000 times of 100ns.
         */
        LONGLONG DueTime = -10000000LL / g_RefreshRate;

        EXT_SET_PARAMETERS params;
        ExInitializeSetTimerParameters(&params);
        ExSetTimer(pDevExt->VSyncTimer, DueTime, 0, &params);
    }

    pDevExt->bVSyncTimerEnabled = !!fEnable;

    return STATUS_SUCCESS;
}

void vboxWddmDiInitDefault(DXGK_DISPLAY_INFORMATION *pInfo,
                           PHYSICAL_ADDRESS PhAddr,
                           D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId)
{
    pInfo->Width = 1920;
    pInfo->Height = 1080;
    pInfo->Pitch = pInfo->Width * 4;
    pInfo->ColorFormat = D3DDDIFMT_A8R8G8B8;
    pInfo->PhysicAddress = PhAddr;
    pInfo->TargetId = VidPnSourceId;
    pInfo->AcpiId = 0;
}

void vboxWddmDiToAllocData(PVBOXMP_DEVEXT pDevExt,
                           const DXGK_DISPLAY_INFORMATION *pInfo,
                           struct VBOXWDDM_ALLOC_DATA *pAllocData)
{
    pAllocData->SurfDesc.width = pInfo->Width;
    pAllocData->SurfDesc.height = pInfo->Height;
    pAllocData->SurfDesc.format = pInfo->ColorFormat;
    pAllocData->SurfDesc.bpp = vboxWddmCalcBitsPerPixel(pInfo->ColorFormat);
    pAllocData->SurfDesc.pitch = pInfo->Pitch;
    pAllocData->SurfDesc.depth = 1;
    pAllocData->SurfDesc.slicePitch = pInfo->Pitch;
    pAllocData->SurfDesc.cbSize = pInfo->Pitch * pInfo->Height;
    pAllocData->SurfDesc.VidPnSourceId = pInfo->TargetId;
    pAllocData->SurfDesc.RefreshRate.Numerator = g_RefreshRate * 1000;
    pAllocData->SurfDesc.RefreshRate.Denominator = 1000;

    /* the address here is not a VRAM offset! so convert it to offset */
    vboxWddmAddrSetVram(&pAllocData->Addr, 1,  vboxWddmVramAddrToOffset(pDevExt, pInfo->PhysicAddress));
}
